﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using FBGraph.Web.Configuration;
using FBGraph.Web.Security;

namespace FBGraph.Web.Mvc
{
    /// <summary>An <see cref="IAuthorizationFilter" /> implementation based off Facebook's authorization and extended permissions systems.</summary>
    /// <remarks>
    /// The presence of the <see cref="FBGraphAuthorizeAttribute" /> on a controller or action will require the user's authorization of the application.
    /// </remarks>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
    public sealed class FBGraphAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter, IFBGraphAuthorizationHandler
    {
        /// <summary>Initializes an instance of <see cref="FBGraphAuthorizeAttribute" /> that does not require any extended permissions.</summary>
        public FBGraphAuthorizeAttribute() { }

        /// <summary>Initializes an instance of <see cref="FBGraphAuthorizeAttribute" /> that does requires the extended permissions specified by <paramref name="scope" />.</summary>
        /// <param name="scope">A mapping of <see cref="ExtendedPermission" />s that are required to be granted by the user accessing the decorated controller or action.</param>
        public FBGraphAuthorizeAttribute(ExtendedPermission scope)
        {
            this.Scope = scope;
        }

        /// <summary>Gets the mapping of <see cref="ExtendedPermission" />s that are required to be granted by the user accessing the decorated controller or action.</summary>
        public ExtendedPermission? Scope { get; private set; }

        /// <summary>Checks the user's known set of granted <see cref="ExtendedPermission" />s.</summary>
        /// <param name="httpContext">The HTTP context.</param>
        /// <returns><c>true</c> if the user is known to have granted the specified set of permissions; otherwise <c>false</c>.</returns>
        protected override Boolean AuthorizeCore(HttpContextBase httpContext)
        {
            if (httpContext == null) throw new NullReferenceException("httpContext");

            return this.Authorize(httpContext, this.Scope ?? default(ExtendedPermission));
        }

        /// <summary>Checks for the required extended permissions, and redirects to Facebook's authorization page if all requirements are not met.</summary>
        /// <param name="filterContext">The filter context.</param>
        /// <remarks>
        /// For AJAX requests, a redirect is not made; rather, the url the user should be redirected to is added to <see cref="HttpContextBase.Items" /> with the key
        /// <c>FBGraphAuthorizeAttribute_Redirect</c>. This url must be returned to the client so that a redirect can be executed.
        /// </remarks>
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null) throw new ArgumentNullException("filterContext");

            if (this.AuthorizeCore(filterContext.HttpContext))
            {
                var cache = filterContext.HttpContext.Response.Cache;
                cache.SetProxyMaxAge(new TimeSpan(0L));
                cache.AddValidationCallback(new HttpCacheValidateHandler(this.CacheValidateHandler), null);
            }
            else
            {
                filterContext.Result = new RequiresAuthorizationResult(this.Scope ?? default(ExtendedPermission));
            }
        }

        private void CacheValidateHandler(HttpContext context, Object data, ref HttpValidationStatus validationStatus)
        {
            validationStatus = this.OnCacheAuthorization(new HttpContextWrapper(context));
        }
    }
}